/*
This software is subject to the license described in the License.txt file
included with this software distribution. You may not use this file except in compliance
with this license.

Copyright (c) Dynastream Innovations Inc. 2014
All rights reserved.
*/

/**@file
 * @defgroup asc_slave module
 * @{
 * @ingroup ant_auto_shared_channel
 *
 * @brief ANT Auto Shared Channel (ASC) Slave implementations.
 */

#include "asc_slave.h"
#include <stdint.h>
#include <string.h>
#include "ant_interface.h"
#include "ant_parameters.h"
#include "app_error.h"
#include "asc_events.h"
#include "asc_parameters.h"
#include "nrf_soc.h"
#include "nrf.h"
#include "asc_pages.h"

// Private Definitions
#define ANT_SEARCH_TIMEOUT          ((uint8_t) 120)         /**< The ANT channel search timeout that will be used when discovering an ASC master. */

#define MODEL_NUMBER                ((uint16_t) 1)          /**< The model number of this ASC Slave. */
#define HW_REVISION                 ((uint8_t) 1)           /**< The hardware revision number of this ASC Slave. */
#define SW_REVISION                 ((uint8_t) 1)           /**< The software revision number of this ASC Slave. */

#define WAITING_TIMEOUT             ((uint8_t) 3)           /**< The maximum amount of time to stay in the WAITING state before falling back to the SEARCHING state. */
#define CONFIRMING_TIMEOUT          ((uint8_t) 2)           /**< The maximum amount of time to stay in the CONFIRMING state before falling back to the SEARCHING state. */
#define DEFAULT_BACKOFF_RANGE       ((uint8_t) 100)         /**< The maximum backoff value that an ASC Slave will employ if no valid backoff range is given by the ASC Master. */
#define MIN_BACKOFF                 ((uint8_t) 2)           /**< The minimum number of message periods to wait until attempting to register with a master. */


// Private Variables
static uint32_t                 m_timeout_counter = 0;      /**< The counter used to determine timeouts. Incremented using @link ascs_increment_timer. */
static uint32_t                 m_serial_number;            /**< The serial number of this slave device. */
static uint8_t                  m_tx_buffer[ANT_STANDARD_DATA_PAYLOAD_SIZE] = {0};   /**< The buffer used for normal transmission. */
static uint8_t                  m_response_buffer[ANT_STANDARD_DATA_PAYLOAD_SIZE] = {0};   /**< The buffer used for responding to data page requests. */
static uint8_t                  m_backoff_range = 0;        /**< The maximum number of message periods to wait until attempting to register with a master (changes depending on how long the device has been waiting to register). */
static uint8_t                  m_backoff = 0;              /**< The number of message periods the slave device. */
                                                            /**< should backoff for before attempting to register with a master. */
static uint8_t                  m_data_timeout;             /**< Data timeout specified by master. */

#ifdef TWO_BYTE_SHARED_ADDRESS
    static uint16_t             m_shared_address;           /**< The shared address assigned to the slave device (two byte version). */
#else
    static uint8_t              m_shared_address;           /**< The shared address assigned to the slave device (one byte version). */
#endif
static uint16_t                 m_groups_assigned = NO_GROUPS;  /**< Bitfield for storing groups currently assigned to. */

static uint8_t                  m_channel_number;           /**< The channel number that the ASC Slave is to use. */
static ascs_states_t            m_state = OFF;              /**< The current state of the ASC Slave. */
static asc_slave_states_t       m_light_state = SLAVE_STATE_OFF;    /**< The current state of the ASC Slave's light. */
static asc_request_data_t       m_last_received_request;    /**< The details of the last request that the ASC Slave has received. */
static uint32_t                 m_asc_event_flags = 0;      /**< Bitfield describing the current events that may need attention by the main application. */


// Private Functions
/**@brief Sets the state machine to the specified state.
 */
static void ascs_set_state(ascs_states_t new_state)
{
    uint32_t err_code;

    if (m_state != new_state)
    {
        switch (new_state)
        {
            case OFF:
            {
                m_state = new_state;
                err_code = sd_ant_channel_close(m_channel_number);
                APP_ERROR_CHECK(err_code);
                break;
            }
            case SEARCHING:
            {
                //Check if we need to re-open the channel
                if (m_state == OFF)
                {
                    //Open slave channel
                    err_code = sd_ant_channel_open(m_channel_number);
                    APP_ERROR_CHECK(err_code);
                }

                m_state = new_state;
                m_groups_assigned = NO_GROUPS;
                m_shared_address = SEARCH_ADDRESS;
                asc_encode_set_shared_address(m_shared_address, m_tx_buffer);
                err_code = sd_ant_broadcast_message_tx (m_channel_number,  ANT_STANDARD_DATA_PAYLOAD_SIZE, m_tx_buffer);
                APP_ERROR_CHECK(err_code);
                break;
            }
            case REQUESTING:
            {
                if (m_state != SEARCHING)
                {
                    asc_event_set(&m_asc_event_flags, EVENT_ASC_DEVICE_IN_WRONG_STATE);
                    return;
                }
                m_state = new_state;
                asc_encode_request_address_page(m_serial_number, m_tx_buffer);
                err_code = sd_ant_acknowledge_message_tx(m_channel_number,  ANT_STANDARD_DATA_PAYLOAD_SIZE, m_tx_buffer);
                APP_ERROR_CHECK(err_code);

                break;
            }
            case WAITING:
            {
                if (m_state != REQUESTING)
                {
                    asc_event_set(&m_asc_event_flags, EVENT_ASC_DEVICE_IN_WRONG_STATE);
                    return;
                }
                m_state = new_state;
                m_shared_address = REGISTER_ADDRESS;
                asc_encode_set_shared_address(m_shared_address, m_tx_buffer);
                err_code = sd_ant_broadcast_message_tx (m_channel_number,  ANT_STANDARD_DATA_PAYLOAD_SIZE, m_tx_buffer);
                APP_ERROR_CHECK(err_code);
                break;
            }
            case CONFIRMING:
            {
                if (m_state != WAITING)
                {
                    asc_event_set(&m_asc_event_flags, EVENT_ASC_DEVICE_IN_WRONG_STATE);
                    return;
                }
                m_state = new_state;
                m_timeout_counter = 0;
                asc_confirm_acquire_paramters_t params = {
                    .serial_number = m_serial_number,
                    .model_number = MODEL_NUMBER,
                    .hw_revision = HW_REVISION,
                    .sw_revision = SW_REVISION
                };
                asc_encode_confirm_acquire_page(params, m_tx_buffer);
                err_code = sd_ant_acknowledge_message_tx(m_channel_number,  ANT_STANDARD_DATA_PAYLOAD_SIZE, m_tx_buffer);
                APP_ERROR_CHECK(err_code);
                break;
            }
            case ASSIGNED:
            {
                if (m_state != CONFIRMING)
                {
                    asc_event_set(&m_asc_event_flags, EVENT_ASC_DEVICE_IN_WRONG_STATE);
                    return;
                }
                ascs_states_t previous_state = m_state;
                m_state = new_state;
                m_timeout_counter = 0;
                if (previous_state == CONFIRMING)
                {
                    asc_encode_set_shared_address(m_shared_address, m_tx_buffer);
                    err_code = sd_ant_broadcast_message_tx (m_channel_number,  ANT_STANDARD_DATA_PAYLOAD_SIZE, m_tx_buffer);
                    APP_ERROR_CHECK(err_code);
                }
                break;
            }
            default:
            {
                asc_event_set(&m_asc_event_flags, EVENT_ASC_DEVICE_IN_WRONG_STATE);
                return;
            }
        }
        asc_event_set(&m_asc_event_flags, EVENT_ASC_STATE_CHANGED);
    }
}


/** @brief Function to decode and handle a command message.
*/
static void ascs_process_command(uint8_t * p_event_message_buffer)
{
    uint32_t err_code;

    asc_command_data_t command_data;
    err_code = asc_decode_command_page(&command_data, p_event_message_buffer);
    APP_ERROR_CHECK(err_code);

    switch (command_data.command)
    {
        case TURN_OFF:
        {
            if (command_data.group_number == NO_GROUPS ||
               (GROUP_SET << command_data.group_number) & m_groups_assigned)
            {
                m_light_state = SLAVE_STATE_OFF;
            }
            break;
        }
        case TURN_ON:
        {
            if (command_data.group_number == NO_GROUPS ||
               (GROUP_SET << command_data.group_number) & m_groups_assigned)
            {
                m_light_state = SLAVE_STATE_ON;
            }
            break;
        }
        case ASSIGN_TO_GROUP:
        {
            m_groups_assigned = GROUP_SET << command_data.group_number;
            break;
        }
        case ASSIGN_TO_ADDITIONAL_GROUP:
        {
            m_groups_assigned |= GROUP_SET << command_data.group_number;
            break;
        }
        case ASSIGN_TO_ALL_GROUPS:
        {
            m_groups_assigned = ALL_GROUPS;
            break;
        }
        case CLEAR_FROM_GROUP:
        {
//lint --e{502}
            m_groups_assigned &= ~(GROUP_SET << command_data.group_number);
            break;
        }
        case CLEAR_FROM_ALL_GROUPS:
        {
            m_groups_assigned = NO_GROUPS;
            break;
        }
        default:
        {
            break;
        }
    }

    asc_event_set(&m_asc_event_flags, EVENT_ASC_LIGHT_STATE_CHANGED);
}

/**@brief Processes a ANT messages while in the off state.
 *
 * @param[in] Pointer to the raw ant message received.
 */
static void ascs_process_message_off(uint8_t event, uint8_t * p_event_message_buffer)
{
    //Do nothing
}


/** @brief Processes a ANT messages while in the searching state.
 *
 * @param[in] Pointer to the raw ant message received.
 */
static void ascs_process_message_searching(uint8_t event, uint8_t * p_event_message_buffer)
{
    uint32_t err_code;
    switch (event)
    {
        case EVENT_RX:
        {
            //Check if we are set to backoff
            if (m_backoff == 0)
            {
                //Process by page
                if (p_event_message_buffer[DECODE_PAGE_ID_BYTE] == ADDRESS_AVAILABLE_PID)
                {
                    asc_addr_available_paramters_t addr_avail_params;
                    err_code = asc_decode_address_available_page(&addr_avail_params, p_event_message_buffer);
                    APP_ERROR_CHECK(err_code);

                    if (addr_avail_params.is_address_available)
                    {
                        m_backoff_range = addr_avail_params.backoff_range;
                        if (m_backoff_range == INVALID_BACKOFF_RANGE)
                        {
                            m_backoff_range = DEFAULT_BACKOFF_RANGE;
                        }

                        //Match the message period of the hub
                        err_code = sd_ant_channel_period_set(m_channel_number, asc_get_counts_from_period_enum(addr_avail_params.channel_period));
                        APP_ERROR_CHECK(err_code);

                        //Multiply data timeout by 2 to get total timeout in seconds
                        m_data_timeout = addr_avail_params.data_timeout;

                        ascs_set_state(REQUESTING);
                    }
                    else
                    {
                        /**@todo decide what to do in this case? off state? */
                    }
                }
            }
            else
            {
                m_backoff--;
            }
            break;
        }
        case EVENT_CHANNEL_CLOSED:
        {
            ascs_set_state(OFF);
            break;
        }
        default:
        {
            break;
        }
    }
}


/** @brief Processes a ANT messages while in the requesting state.
 *
 * @param[in] Pointer to the raw ant message received.
 */
static void ascs_process_message_requesting(uint8_t event, uint8_t * p_event_message_buffer)
{
    uint32_t err_code;
    switch (event)
    {
        case EVENT_TRANSFER_TX_FAILED:
        {
            //Randomly get a backoff between MIN_BACKOFF to m_backoff_range
            uint8_t num_rand_bytes_available;
            err_code = sd_rand_application_bytes_available_get(&num_rand_bytes_available);
            APP_ERROR_CHECK(err_code);

            //update the backoff if there is a randon number available
            if (num_rand_bytes_available > 0)
            {
                uint8_t rand_number;
                err_code = sd_rand_application_vector_get(&rand_number, 1);
                APP_ERROR_CHECK(err_code);

                m_backoff = (uint8_t) (rand_number % m_backoff_range + MIN_BACKOFF);
            }

            ascs_set_state(SEARCHING);
            break;
        }
        case EVENT_TRANSFER_TX_COMPLETED:
        {
            ascs_set_state(WAITING);
            break;
        }
        case EVENT_CHANNEL_CLOSED:
        case EVENT_RX: //Intentional fallthrough
        default:
        {
            break;
        }
    }
}


/**@brief Processes a ANT messages while in the waiting state.
 *
 * @param[in] Pointer to the raw ant message received.
 */
static void ascs_process_message_waiting(uint8_t event, uint8_t * p_event_message_buffer)
{
    uint32_t err_code;

    switch (event)
    {
        case EVENT_RX:
        {
            //Process by page
            switch (p_event_message_buffer[DECODE_PAGE_ID_BYTE])
            {
                case BUSY_ACQUIRING_PID:
                {
                    bool do_serial_numbers_match;
                    err_code = asc_decode_busy_acquiring_page(m_serial_number, &do_serial_numbers_match, &m_shared_address, p_event_message_buffer);
                    APP_ERROR_CHECK(err_code);

                    if (do_serial_numbers_match)
                    {
                        ascs_set_state(CONFIRMING);
                    }
                    else //There was no match
                    {
                        ascs_set_state(SEARCHING);
                    }
                    break;
                }
                default:
                {
                    break;
                }
            }
            break;
        }
        case EVENT_CHANNEL_CLOSED:
        default:
        {
            break;
        }
    }
}


/** @brief Processes a ANT messages while in the confirming state.
 *
 * @param[in] Pointer to the raw ant message received.
 */
static void ascs_process_message_confirming(uint8_t event, uint8_t * p_event_message_buffer)
{
    switch (event)
    {
        case EVENT_TRANSFER_TX_FAILED:
        {
            int32_t err_code;
            //Re-send the Confirm Acquire page (should still be loaded in the tx buffer).
            err_code = sd_ant_acknowledge_message_tx(m_channel_number,  ANT_STANDARD_DATA_PAYLOAD_SIZE, m_tx_buffer);
            APP_ERROR_CHECK(err_code);
            break;
        }
        case EVENT_TRANSFER_TX_COMPLETED:
        {
            ascs_set_state(ASSIGNED);
            break;
        }
        case EVENT_CHANNEL_CLOSED:
        case EVENT_RX: //Intentional fallthrough
        default:
        {
            break;
        }
    }
}


/**@brief Processes a ANT messages while in the assigned state.
 *
 * @param[in] Pointer to the raw ant message received.
 */
static void ascs_process_message_assigned(uint8_t event, uint8_t * p_event_message_buffer)
{
    uint32_t err_code;
    switch (event)
    {
        case EVENT_RX:
        {
            m_timeout_counter = 0;
            switch (p_event_message_buffer[DECODE_PAGE_ID_BYTE])
            {
                case REQUEST_DATA_PID:
                {
                    err_code = asc_decode_request_data_page(&m_last_received_request, p_event_message_buffer);
                    APP_ERROR_CHECK(err_code);
                    //if the request is for the update data page, handle it automatically.
                    //Otherwise, notify the application that the request came in
                    if (m_last_received_request.page_id_requested == UPDATE_DATA_PID)
                    {
                        asc_update_data_t update = {
                            .shared_address = m_shared_address,
                            .state = m_light_state
                        };
                        asc_encode_update_data_page(update, m_response_buffer);
                        err_code = sd_ant_broadcast_message_tx(m_channel_number,  ANT_STANDARD_DATA_PAYLOAD_SIZE, m_response_buffer);
                        APP_ERROR_CHECK(err_code);
                    }
                    else
                    {
                        asc_event_set(&m_asc_event_flags, EVENT_ASC_REQUEST_RECEIVED);
                    }
                    break;
                }
                case COMMAND_PID:
                {
                   ascs_process_command(p_event_message_buffer);
                    break;
                }
            }
            break;
        }
        default:
        {
            break;
        }
    }
}


// Public Functions
void ascs_init(const asc_ant_params_t * const ant_parameters)
{
    uint32_t    err_code;

    m_asc_event_flags = 0;
    m_channel_number = ant_parameters->channel_number;
    m_serial_number = ((uint32_t)NRF_FICR->DEVICEID[0]) & (NRF_FICR->DEVICEID[0] << 8);

    err_code = sd_ant_channel_assign(m_channel_number,
                                     CHANNEL_TYPE_SHARED_SLAVE,
                                     ant_parameters->network_number,
                                     ASC_ANT_EXT_ASSIGNMENT);
    APP_ERROR_CHECK(err_code);

    err_code = sd_ant_channel_id_set(m_channel_number,
                                     *(ant_parameters->p_device_number),
                                     ant_parameters->device_type,
                                     ant_parameters->tx_type);
    APP_ERROR_CHECK(err_code);

    //Assign slave channel frequency
    err_code = sd_ant_channel_radio_freq_set(m_channel_number, ant_parameters->rf_frequency);
    APP_ERROR_CHECK(err_code);

    //Assign slave channel message period
    err_code = sd_ant_channel_period_set(m_channel_number,
                                        asc_get_counts_from_period_enum(ant_parameters->channel_period));
    APP_ERROR_CHECK(err_code);

    //Set the transmission power
    err_code = sd_ant_channel_radio_tx_power_set(m_channel_number, ant_parameters->tx_power, ASC_ANT_CUSTOM_PWR);
    APP_ERROR_CHECK(err_code);

    //Assign the search timeout
    err_code = sd_ant_channel_rx_search_timeout_set(m_channel_number, ANT_SEARCH_TIMEOUT);
    APP_ERROR_CHECK(err_code);

    // Configure proximity search
    // @todo: JP added trailin 0 paramter
    err_code = sd_ant_prox_search_set(m_channel_number, PROXIMITY_THRESHOLD_5, 0);
    APP_ERROR_CHECK(err_code);
}


void ascs_turn_on(void)
{
    ascs_set_state(SEARCHING);
}


void ascs_handle_ant_event(uint8_t event, uint8_t * p_event_message_buffer)
{
    switch (m_state)
    {
        case OFF:
            ascs_process_message_off(event, p_event_message_buffer);
            break;
        case SEARCHING:
            ascs_process_message_searching(event, p_event_message_buffer);
            break;
        case REQUESTING:
            ascs_process_message_requesting(event, p_event_message_buffer);
            break;
        case WAITING:
            ascs_process_message_waiting(event, p_event_message_buffer);
            break;
        case CONFIRMING:
            ascs_process_message_confirming(event, p_event_message_buffer);
            break;
        case ASSIGNED:
            ascs_process_message_assigned(event, p_event_message_buffer);
            break;
        default:
            break;
    }
}


void ascs_send_data(uint8_t * p_data)
{
}


ascs_states_t ascs_state_get(void)
{
    return m_state;
}


asc_slave_states_t ascs_light_state_get(void)
{
    return m_light_state;
}


uint32_t ascs_events_get(void)
{
    return m_asc_event_flags;
}


void ascs_event_clear(uint32_t event)
{
   asc_event_clear(&m_asc_event_flags, event);
}


void ascs_increment_timer(void)
{
    //Check the timeout counter based on current state
    switch (m_state)
    {
        case REQUESTING:
        {
            break;
        }
        case WAITING:
        {
            if (++m_timeout_counter >= WAITING_TIMEOUT)
            {
                ascs_set_state(SEARCHING);
            }
            break;
        }
        case CONFIRMING:
        {
            if (++m_timeout_counter >= CONFIRMING_TIMEOUT)
            {
                ascs_set_state(SEARCHING);
            }
            break;
        }
        case ASSIGNED:
        {
            if (m_data_timeout != DATA_TIMEOUT_DISABLED && ++m_timeout_counter >= m_data_timeout)
            {
                ascs_set_state(SEARCHING);
            }
            break;
        }
        case SEARCHING:
        case OFF: //Intentional fallthroughs
        default:
        {
            break;
        }
    }
}


asc_request_data_t ascs_get_last_request(void)
{
    return m_last_received_request;
}


void ascs_send_response(uint8_t * response_data_buffer)
{
    uint32_t err_code;
    uint32_t array_size = sizeof(response_data_buffer) / sizeof(response_data_buffer[0]);


    if (array_size != ANT_STANDARD_DATA_PAYLOAD_SIZE)
    {
        err_code = NRF_ERROR_INVALID_PARAM;
    }
    else
    {
        memcpy(m_response_buffer, response_data_buffer, ANT_STANDARD_DATA_PAYLOAD_SIZE);
        err_code = sd_ant_broadcast_message_tx(m_channel_number, ANT_STANDARD_DATA_PAYLOAD_SIZE, m_response_buffer);
    }

    APP_ERROR_CHECK(err_code);
}

/** @} */
